package org.cweili.rayminder.repository.h2

import groovy.sql.DataSet
import groovy.sql.Sql
import org.apache.commons.lang3.reflect.FieldUtils
import org.apache.commons.logging.Log
import org.apache.commons.logging.LogFactory
import org.cweili.rayminder.repository.BaseRepository
import org.cweili.rayminder.util.Global
import org.cweili.rayminder.util.Utils

import javax.persistence.Column
import javax.persistence.Id
import javax.persistence.Transient
import java.lang.reflect.Field
import java.lang.reflect.Modifier
/**
 *
 * @author Cweili
 * @version 2013年8月14日 下午7:36:21
 *
 */
abstract class BaseRepositoryH2<T> implements BaseRepository<T, Integer> {

	protected static final Sql sql = Sql.newInstance(
			"jdbc:h2:tcp://localhost:52012/db/${Global.APP_NAME};AUTO_RECONNECT=TRUE;TRACE_LEVEL_SYSTEM_OUT=2",
			'sa', '', 'org.h2.Driver')
	protected static final Log log = LogFactory.getLog(BaseRepositoryH2)

	protected String selectSql
	protected String deleteSql

	Class entityClass
	String tableName
	DataSet data

	BaseRepositoryH2(Class entityClass) {
		super()
		this.entityClass = entityClass
		tableName = entityClass.simpleName.toLowerCase()
		selectSql = 'SELECT * FROM ' + tableName + ' WHERE '
		deleteSql = 'DELETE FROM ' + tableName + ' WHERE '
		data = sql.dataSet(tableName)
		initialize()
	}

	@Override
	boolean initialize() {
		try {
			count()
			true
		} catch (Exception e) {
			def ddl = new StringBuilder("CREATE TABLE ${tableName} (")
			for (field in entityClass.getDeclaredFields()) {
				if (isPropertity(field)) {
					ddl.append(field.name).append(initFieldType(field))
					if (field.isAnnotationPresent(Id)) {
						ddl.append(' PRIMARY KEY')
					} else if (field.isAnnotationPresent(Column)) {
						Column column = field.getAnnotation(Column)
						if (column.unique()) {
							ddl.append(' UNIQUE')
						}
					}
					ddl.append(',')
				}
			}
			ddl.deleteCharAt(ddl.length() - 1)
			ddl.append(')')
			log.info("H2 initialize: ${ddl}")
			try {
				sql.execute(ddl.toString())
			} catch (Exception e1) {
				log.error(e1, e1)
				Utils.quit()
			}
			false
		}
	}


	@Override
	T save(T entity) {
		if (!entity) {
			return null
		}
		if (!entity.id || !findOne(entity.id)) {
			entity.id = Utils.generateId()
			def map = convertToMap(entity)
			data.add(map)
			log.info("H2 insert: ${entity}")
		} else {
			def updateSql = buildUpdateSql(entity)
			sql.execute(updateSql, entity)
			log.info("H2 update: ${updateSql} ${entity}")
		}
		findOne(entity.id)
	}

	@Override
	Iterable<T> save(Iterable<T> entities) {
		for (entity in entities) {
			save(entity)
		}
		entities
	}

	@Override
	T findOne(Integer id) {
		def resultSet = sql.firstRow(selectSql + 'id=?', [id])
		log.info("H2 find: ${resultSet}")
		convertToBean(resultSet)
	}

	@Override
	boolean exists(Integer id) {
		findOne(id)
	}

	@Override
	Iterable<T> findAll() {
		def list = []
		sql.eachRow('SELECT * FROM ' + tableName) { entity ->
			list.add(convertToBean(entity))
		}
		list
	}

	@Override
	Iterable<T> findAll(Iterable<Integer> ids) {
		def list = []
		for (id in ids) {
			list.add(findOne(id))
		}
		list
	}

	@Override
	long count() {
		log.info("H2 count: ${tableName}")
		sql.firstRow('SELECT COUNT(*) AS c FROM ' + tableName).c
	}

	@Override
	void delete(Integer id) {
		log.info("H2 delete: ${tableName}.id=${id}")
		sql.execute(deleteSql + 'id=?', [id])
	}

	@Override
	void delete(T entity) {
		log.info("H2 delete: ${tableName}.id=${entity.id}")
		sql.execute(deleteSql + 'id=?', [entity.id])
	}

	@Override
	void delete(Iterable<? extends T> entities) {
		for (entity in entities) {
			delete(entity)
		}
	}

	@Override
	void deleteAll() {
		log.info("H2 truncate table: ${tableName}")
		sql.execute('TRUNCATE TABLE ' + tableName)
	}

	protected T convertToBean(Map map) {
		if (!map || map.size() == 0) {
			return null
		}
		T entity = (T) entityClass.newInstance()
		for (field in entityClass.getDeclaredFields()) {
			if (isPropertity(field)) {
				FieldUtils.writeField(field, entity, map.get(field.name.toUpperCase()), true)
			}
		}
		entity
	}

	protected Map convertToMap(entity) {
		if (!entity) {
			return null
		}
		def map = [:]
		def properties = entity.getProperties()
		for (field in entityClass.getDeclaredFields()) {
			isPropertity(field) && map.put(field.name, properties.get(field.name))
		}
		map
	}

	protected String buildUpdateSql(entity) {
		def updateSql = new StringBuilder("UPDATE ${tableName} SET ")
		for (field in entityClass.getDeclaredFields()) {
			if (isPropertity(field) && 'id' != field.name) {
				updateSql.append(field.name).append('=?.')
						.append(field.name).append(',')
			}
		}
		updateSql.deleteCharAt(updateSql.length() - 1)
		updateSql.append(' WHERE id=?.id').toString()
	}

	protected static boolean isPropertity(Field field) {
		!field.isAnnotationPresent(Transient) && field.getModifiers() == Modifier.PRIVATE
	}

	private static initFieldType(Field field) {
		def type = field.type
		if (CharSequence.class.isAssignableFrom(type)) {
			' VARCHAR(4096)'
		} else if (Boolean.isAssignableFrom(type)) {
			' BOOLEAN'
		} else if (Byte.isAssignableFrom(type)) {
			' TINYINT'
		} else if (Integer.isAssignableFrom(type)) {
			' INT'
		} else if (Long.isAssignableFrom(type)) {
			' BIGINT'
		} else if (Float.isAssignableFrom(type)) {
			' REAL'
		} else if (Double.isAssignableFrom(type)) {
			' DOUBLE'
		} else if (Number.isAssignableFrom(type)) {
			' INT'
		} else if (Date.isAssignableFrom(type)) {
			' DATETIME'
		} else if (InputStream.isAssignableFrom(type)) {
			' BLOB'
		} else if (type.isArray() && Byte.isAssignableFrom(type.getComponentType())) {
			' BINARY(4096)'
		} else {
			' OTHER'
		}
	}
}
